// vim: set ft=groovy ts=4 sw=4:
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// *
// *              C E D A R
// *          S O L U T I O N S       "Software done right."
// *           S O F T W A R E
// *
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// *
// * Copyright (c) 2013-2015 Kenneth J. Pronovici.
// * All rights reserved.
// *
// * This program is free software; you can redistribute it and/or
// * modify it under the terms of the Apache License, Version 2.0.
// * See LICENSE for more information about the licensing terms.
// *
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// *
// * Author   : Kenneth J. Pronovici <pronovic@ieee.org>
// * Language : Gradle (>= 1.7)
// * Project  : Common Java Functionality
// *
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// See: http://stackoverflow.com/questions/1163173/why-use-gradle-instead-of-ant-or-maven

// ***************
// * Gradle setup
// ***************

// Sources for the build script itself
buildscript {
    repositories {
        mavenCentral()
        mavenLocal()   // Local Maven repository
    }

    dependencies {
        classpath "com.googlecode.cedar-common:cedar-build:0.8.14"
    }
}

// Plugins
apply plugin: "eclipse"
apply plugin: "cedarBuild"
apply plugin: "cedarCopyright"

// Load properties from standard configuration files (build.properties and local.properties)
cedarProperties.loadStandardProperties()

// Configure gradle so the parent project is always executed last
// This is necessary so we only label code after everything has been published
evaluationDependsOnChildren()


// ************************
// * Project configuration
// ************************

// Configuration for labeling the code
cedarLabel {
    enabled = config_mercurialLabelEnabled
    projectName = "CedarCommon"
    projectVersion = config_mavenPublishVersion
    repositories = [ projectDir, ]
    mercurialPath = config_mercurialPath
}

// Configuration for updating copyright statements
cedarCopyright {
    licensePattern = '^.*LICENSE$'
    sourcePatterns = [ '^.*\\.java$', '^.*\\.rb$', '^.*\\.feature$', '^.*\\.gradle$', '^.*build.xml$', ]
    repositories = [ projectDir, ]
    mercurialPath = config_mercurialPath
}

// Configuration for digital signatures
cedarSigning {
    gpgKeyId = config_gpgKeyId
    gpgSecretKey = config_gpgSecretKey
    projects = allprojects
}

// Configuration for all projects
allprojects {
    apply plugin: "eclipse"
    apply plugin: "java"
    compileJava.options.fork(memoryMaximumSize: config_javaCompilerMemory)
}

// Common setup for subprojects
subprojects {

    apply plugin: "cedarBuild"
    apply plugin: "cedarPublish"
    apply plugin: "cedarJavadoc"

    group = "com.googlecode.cedar-common"
    version = config_mavenPublishVersion
    buildDir = { "../build/${project.name}" }

    sourceCompatibility = JavaVersion.VERSION_1_6    // because GWT 2.5.1 does not support Java 7
    targetCompatibility = JavaVersion.VERSION_1_6    // to be as easy-to-use as possible for other projects

    repositories {
        mavenLocal()                                       // Local Maven repository
        mavenCentral()                                     // Maven Central repository
        maven { url "http://download.java.net/maven/2/" }  // JavaMail and related code
    }

    cedarSigning {
        gpgKeyId = config_gpgKeyId
        gpgSecretKey = config_gpgSecretKey
        projects = allprojects
    }

    cedarPublish {
        mavenRepositoryUrl = config_mavenRepositoryUrl
        mavenRepositoryUser = config_mavenRepositoryUser
    }

    cedarJavadoc {
        title = "cedar-common-${project.name} ${config_mavenPublishVersion} API"
        output = "${project.docsDir}/javadoc"
        classpath = { project.configurations.runtime }
        srcDirs = { sourceSets.main.java.srcDirs }
        subpackages = [ "com", "biz", ]
    }

    jar {
        from sourceSets.main.output
        from sourceSets.main.allSource
        excludes = [ ".settings", ".project", ".classpath", ".gradle", "build", ]
    }

    task sourcesJar(type: Jar) {
        classifier = "sources"
        from sourceSets.main.allSource
        excludes = [ ".settings", ".project", ".classpath", ".gradle", "build", ]
    }

    task javadocJar(type: Jar, dependsOn: jdoc) {
        classifier = "javadoc"
        archiveName = "cedar-common-${baseName}-${version}-${classifier}.${extension}"
        from "${project.docsDir}/javadoc"
    }

    artifacts {
        archives jar
        archives sourcesJar
        archives javadocJar
    }

    signing {
        required = { cedarPublish.isSignatureRequired() }
        sign jar
        sign sourcesJar
        sign javadocJar
    }

    uploadArchives {
        repositories {
            mavenDeployer {
                repository(url: cedarPublish.getPublishRepositoryUrl()) {
                    authentication(userName: cedarPublish.getMavenRepositoryUser(), password: project.convention.plugins.cedarPublish.getMavenRepositoryPassword())
                }
                beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
                pom.artifactId = "cedar-common-${project.name}"
                pom.project {
                    url "https://code.google.com/p/cedar-common/"
                    scm {
                        url "scm:hg:https://code.google.com/p/cedar-common/"
                        connection "scm:hg:https://code.google.com/p/cedar-common/"
                        developerConnection "scm:hg:https://code.google.com/p/cedar-common/"
                    }
                    licenses {
                        license {
                            name "The Apache Software License, Version 2.0"
                            url "http://www.apache.org/license/LICENSE-2.0.txt"
                            distribution "repo"
                        }
                    }
                    developers {
                        developer {
                            id "pronovic"
                            name "Kenneth J. Pronovici"
                            email "pronovic@ieee.org"
                        }
                  }
               }
           }
        }
    }

    install {
        repositories.mavenInstaller {
            pom.artifactId = "cedar-common-${project.name}"
            pom.version = config_mavenPublishVersion
        }
    }

    project.tasks.javadoc.doFirst {
        throw new Exception("Do not use javadoc task, use generateJavadoc instead.")
    }
}

// Subproject for cedar-common-util
project(":util") {

    sourceSets {
        main {
            java { srcDirs = [ ".", "../externalutil", ] }
            resources { srcDirs = [ ".", "../externalutil", ] }
        }
    }

    dependencies {
        compile "com.flipthebird.gwt-hashcode-equals:gwt-hashcode-equals:0.1.0"
        compile "com.google.code.gson:gson:1.7.2"
        compile "commons-beanutils:commons-beanutils:1.8.3"
        compile "commons-codec:commons-codec:1.9"
        compile "commons-lang:commons-lang:2.6"
        compile "javax.servlet:servlet-api:2.5"
        compile "joda-time:joda-time:2.5"
        compile "log4j:log4j:1.2.17"
        compile "org.springframework:spring-beans:3.2.9.RELEASE"
        compile "org.springframework:spring-context:3.2.9.RELEASE"
        compile "org.springframework:spring-core:3.2.9.RELEASE"
        compile "org.springframework:spring-web:3.2.9.RELEASE"
        compile "org.springframework:spring-webmvc:3.2.9.RELEASE"
    }

    uploadArchives {
        repositories {
            mavenDeployer {
                pom.project {
                    name "Cedar Common Utility Library"
                    description "General utility code."
                }
            }
        }
    }

}

// Subproject for cedar-common-gwt
project(":gwt") {

    sourceSets {
        main {
            java { srcDirs = [ ".", "../externalgwt", ] }
            resources { srcDirs = [ ".", "../externalgwt", ] }
        }
    }

    dependencies {
        compile "com.google.gwt:gwt-dev:${config_gwtVersion}"
        compile "com.google.gwt:gwt-user:${config_gwtVersion}"
        compile "com.google.gwt.inject:gin:2.1.2"
        compile "com.googlecode.mvp4g:mvp4g:1.5.0"
        compile "com.googlecode.cedar-common:gwt-sl:1.2"
        compile project(":util")
    }

    configurations {
        all*.exclude group: "asm" // the ASM from GIN conflicts with the one in GWT 2.7.x
    } 

    uploadArchives {
        repositories {
            mavenDeployer {
                pom.project {
                    name "Cedar Common GWT Library"
                    description "Utility code for use with GWT and Mvp4g."
                }
            }
        }
    }
}

// Subproject for cedar-common-gae
project(":gae") {

    sourceSets {
        main {
            java { srcDirs = [ ".", ] }
            resources { srcDirs = [ ".", ] }
        }
    }

    dependencies {
        compile "com.google.appengine:appengine-api-1.0-sdk:${config_appEngineVersion}"
        compile "com.googlecode.cedar-common:objectify:3.1"
        compile "commons-io:commons-io:2.4"
        compile "javax.mail:mail:1.4.4"
        compile "javax.servlet:servlet-api:2.5"
        compile "org.apache.velocity:velocity:1.7"
        compile "org.apache.velocity:velocity-tools:2.0"
        compile "org.springframework.security:spring-security-core:2.0.8.RELEASE"
        compile "org.springframework.security:spring-security-core-tiger:2.0.8.RELEASE"
        compile project(":util")
        compile project(":gwt")

        runtime "org.apache.geronimo.specs:geronimo-jpa_3.0_spec:1.1.1"
        runtime "javax.jdo:jdo2-api:2.3-eb"
    }

    uploadArchives {
        repositories {
            mavenDeployer {
                pom.project {
                    name "Cedar Common GAE Library"
                    description "Utility code for use with GWT on Google App Engine."
                }
            }
        }
    }

}

// Subproject for cedar-common-testutil
project(":testutil") {

    sourceSets {
        main {
            java { srcDirs = [ ".", ] }
            resources { srcDirs = [ ".", ] }
        }
    }

    dependencies {
        compile "javax.servlet:servlet-api:2.5"
        compile "junit:junit:4.11"
        compile "org.mockito:mockito-all:1.10.8"
        compile "org.springframework:spring-test:3.2.9.RELEASE"
        compile project(":util")
    }

    uploadArchives {
        repositories {
            mavenDeployer {
                pom.project {
                    name "Cedar Common Test Utilities"
                    description "Utility code for use with junit, such as assertions and test setup mechanisms."
                }
            }
        }
    }

}

// Subproject for cedar-common-gwttestutil
project(":gwttestutil") {

    sourceSets {
        main {
            java { srcDirs = [ ".", ] }
            resources { srcDirs = [ ".", ] }
        }
    }

    dependencies {
        compile "com.google.gwt:gwt-user:${config_gwtVersion}"
        compile "junit-addons:junit-addons:1.4"
        compile "junit:junit:4.11"
        compile "org.javassist:javassist:3.18.2-GA"
        compile "org.mockito:mockito-all:1.10.8"
        compile project(":util")
    }

    uploadArchives {
        repositories {
            mavenDeployer {
                pom.project {
                    name "Cedar Common GWT Test Utilities"
                    description "Utility code for use in testing GWT, including a stubbed test runner using Mockito."
                }
            }
        }
    }

}

// Subproject for cedar-common-gaetestutil
project(":gaetestutil") {

    sourceSets {
        main {
            java { srcDirs = [ ".", ] }
            resources { srcDirs = [ ".", ] }
        }
    }

    dependencies {
        compile "com.google.gwt:gwt-user:${config_gwtVersion}"
        compile "junit-addons:junit-addons:1.4"
        compile "junit:junit:4.11"
        compile "org.javassist:javassist:3.18.2-GA"
        compile "org.mockito:mockito-all:1.10.8"
        compile project(":util")
        compile project(":gae")
        compile project(":testutil")

        runtime "com.google.appengine:appengine-api-stubs:${config_appEngineVersion}"
        runtime "com.google.appengine:appengine-testing:${config_appEngineVersion}"
    }

    uploadArchives {
        repositories {
            mavenDeployer {
                pom.project {
                    name "Cedar Common GAE Test Utilities"
                    description "Utility code for use in testing GWT applications on Google App Engine."
                }
            }
        }
    }

}

// Unit tests
project(":test") {

    uploadArchives.enabled = false
    install.enabled = false

    sourceSets {
        test {
            java { srcDirs = [ ".", "../externaltest", "../gradle/src/suites", ] }
            resources { srcDirs = [  ".", "../externaltest", "../gradle/src/suites", ] }
            runtimeClasspath += files(project(":util").sourceSets.main.java.srcDirs)
            runtimeClasspath += files(project(":gwt").sourceSets.main.java.srcDirs)
            runtimeClasspath += files(sourceSets.test.java.srcDirs)
        }
    }

    cedarJavadoc {
        srcDirs = []
    }

    dependencies {
        testCompile "com.googlecode.cedar-common:cpsuite:1.2.6"
        testCompile project(":util")
        testCompile project(":gwt")
        testCompile project(":gae")
        testCompile project(":testutil")
        testCompile project(":gwttestutil")
        testCompile project(":gaetestutil")
    }

    // Run unit tests, assumed to be found in a class suites/UnitTestSuite.
    // The caller that applies this plugin still has responsibility for making sure the suite gets compiled.
    project.task("unittest", type: com.cedarsolutions.gradle.TestTask) {

        // these configuration values are set when the task is created
        workingDir = "../"
        scanForTestClasses = false
        enableAssertions = false
        outputs.upToDateWhen { false }
        include "suites/UnitTestSuite.class"

        // these configuration values are set immediately before the test is executed
        deferredConfig {
            setMaxHeapSize(config_unittestMemory)
        }

    }

    // Run GWT client tests, assumed to be found in a class suites/ClientTestSuite.
    // The caller that applies this plugin still has responsibility for making sure the suite gets compiled.
    project.task("clienttest", type: com.cedarsolutions.gradle.TestTask) {

        // these configuration values are set when the task is created
        workingDir = "../"
        scanForTestClasses = false
        scanForTestClasses = false
        enableAssertions = false
        outputs.upToDateWhen { false }
        systemProperty "gwt.args", "-out www-test -logLevel ERROR"
        systemProperty "java.awt.headless", "true"   // required on Linux to avoid deferred binding errors
        include "suites/ClientTestSuite.class"

        // these configuration values are set immediately before the test is executed
        deferredConfig {
            setMaxHeapSize(config_clienttestMemory)
        }

        // delete the cache directories before running the suite
        beforeSuite { descriptor ->
            if (descriptor.className == "suites.ClientTestSuite") {
                def wwwTest = project.file(workingDir.canonicalPath + "/www-test")
                def gwtCache = project.file(workingDir.canonicalPath + "/gwt-unitCache")
                wwwTest.deleteDir()
                gwtCache.deleteDir()
            }
        }

        // delete the cache directories after running the suite
        afterSuite { descriptor ->
            if (descriptor.className == "suites.ClientTestSuite") {
                def wwwTest = project.file(workingDir.canonicalPath + "/www-test")
                def gwtCache = project.file(workingDir.canonicalPath + "/gwt-unitCache")
                wwwTest.deleteDir()
                gwtCache.deleteDir()
            }
        }
    }

    // Effectively disable the standard test runner by making it look for a bogus class.
    project.tasks.test.include("**/bogus.class")

    // Redefine the test runner in terms of the unit and client test suites.
    project.tasks.test.dependsOn(project.tasks.clienttest, project.tasks.unittest)

    // Define the order of tests if there are multiple called at the same time
    project.tasks.clienttest.mustRunAfter project.tasks.unittest

}


// ********************
// * Other build tasks
// ********************

// Label all of the code, only after all subprojects have been published.
// In order to work, this requires evaluationDependsOnChildren().
task publish(dependsOn: subprojects.publish) << {
    tasks.label.execute()
}

// Generate Javadoc for all subprojects into a single directory tree.
// In order to work, this requires evaluationDependsOnChildren().
task generateJavadoc(dependsOn: subprojects.jdoc) << {
    copyJavadocToMercurial(config_mercurialJavadocProject, subprojects, { name -> "cedar-common-" + name })
}


// ************************
// * Eclipse configuration
// ************************
// The eclipse plugin is used to generate Eclipse project files

// Always re-generate everything, otherwise we get duplicate sections in files
tasks.eclipse.dependsOn(cleanEclipse)

// Generate Eclipse classpath info for subprojects first, so all files exist when we need them
tasks.eclipseClasspath.dependsOn(subprojects.eclipseClasspath)

// Clean up extended settings
task cleanEclipseExtendedSettings(type: Delete) << {

    // Delete the settings we copied in
    file(".settings/de.loskutov.anyedit.AnyEditTools.prefs").delete()
    file(".settings/org.eclipse.jdt.core.prefs").delete()
    file(".settings/org.eclipse.jdt.ui.prefs").delete()

    // Remove .settings only if it's empty
    ant.delete(includeemptydirs : "true", quiet : "true") {
        fileset(dir: ".settings", excludes : "**/*")
    }

}

// Clean up the test directories used by Eclipse
task cleanupEclipseTestDirs() << {
    file("gwt-unitCache").deleteDir()
    file("www-test").deleteDir()
}

// Clean up all of the Eclipse test directories whenever everything else is cleaned
tasks.clean.dependsOn(cleanupEclipseTestDirs)

// Clean up custom settings whenever the rest of Eclipse gets cleaned up
tasks.cleanEclipseJdt.dependsOn(cleanEclipseExtendedSettings)

// Copy skeleton settings into the workspace
eclipseJdt << {
    copy {
        from "gradle/eclipse/settings"
        into ".settings"
        include "*.prefs"
    }
}

// Customize the Eclipse .project file
eclipse {
    project {
        natures "org.eclipse.jdt.core.javanature"
        natures "com.google.appengine.eclipse.core.gaeNature"
        natures "com.google.gwt.eclipse.core.gwtNature"
        natures "net.sf.eclipsecs.core.CheckstyleNature"
        natures "net.sourceforge.metrics.nature"

        buildCommand "org.eclipse.jdt.core.javabuilder"
        buildCommand "net.sf.eclipsecs.core.CheckstyleBuilder"
        buildCommand "net.sourceforge.metrics.builder"
    }
}

// Hide the various derived directories from Eclipse
project.eclipse.project.file.withXml { provider ->
    ignoreResourcesFromDirectories(provider, [ ".gradle", "build", "gwt-unitCache", "www-test", "war", ])
}

// Find the source path for a dependency, i.e. gwt-sl-1.2-sources.jar for gwt-sl-1.2.jar
//
// This is really big hack, but I can't figure out any other way to do it.  I'm
// deriving the complete .classpath file by creating a unique list of all
// dependencies in the subprojects.  However, as far as I can tell, I can't ask
// Gradle whether there's a source dependency for a jar.  So, I have to dig
// around in the Gradle cache and see if I can find one.  Yuck.
//
// A jar dependency looks something like this:
//
//    C:\Ken\gradle-cache\caches\artifacts-26\filestore\antlr\antlr\2.7.2\jar\546b5220622c4d9b2da45ad1899224b6ce1c8830\antlr-2.7.2.jar
//
// If there is a source jar, it will look something like this:
//
//    C:\Ken\gradle-cache\caches\artifacts-26\filestore\antlr\antlr\2.7.2\source\b89cfcdcb699c972f9a6a0cdf333a3f39ed33306\antlr-2.7.2-sources.jar
//
// Note that the base directory is the same, but the generated id is not.  We
// have to find the source directory, then find the first subdirectory under
// that, then assume that the first jar in that directory is the source jar.
// Nasty!
//
// @param path  Full path to a jar dependency, as from configurations.runtime.each
// @return The path to the related sources, or null if none appears to exist
def findSourcepath(String path) {
    def sourcepath = null

    def sourcedir = new File(new File(new File(new File(path).parent).parent).parent + "/source")
    if (sourcedir.exists()) {
        sourcedir.eachDir {
            it.listFiles({dir, file-> file ==~ /.*?\.jar/ } as FilenameFilter).each {
                if (sourcepath == null) {
                    sourcepath = it.canonicalPath
                }
            }
        }
    }

    return sourcepath
}

// Customize the Eclipse .classpath file
// By default, Gradle generates one Eclipse project per configured subproject.
// For CedarCommon, I want one big project with a bunch of source folders, so I
// have to implement it myself.
eclipse.classpath.file {
    whenMerged { classpath ->

        // List of subprojects in the proper order
        def projects = [ "util", "gwt", "gae", "testutil", "gwttestutil", "gaetestutil", "test", ]

        // List of source folders in the proper order
        def sources = [ "resources", "util", "gwt", "gae", "testutil", "gwttestutil", "gaetestutil",
                        "test", "externalutil", "externalgwt", "externaltest", "suites", ]

        // List of all the unique libraries among all of the subprojects, ignoring internal builds
        def libraries = new java.util.TreeMap()
        projects.each() {
            project(":${it}").configurations.runtime.each {
                if (!it.canonicalPath.startsWith(buildDir.canonicalPath)) {
                    libraries.put(it.canonicalPath, null)
                }
            }
            project(":${it}").configurations.testCompile.each {
                if (!it.canonicalPath.startsWith(buildDir.canonicalPath)) {
                    libraries.put(it.canonicalPath, null)
                }
            }
        }

        // Remove the default output target
        classpath.entries.removeAll { entry -> entry.kind == "output" }

        // Add each of our source folders in the proper order
        withXml { xml ->
            def node = xml.asNode()
            sources.each() { source ->
                node.appendNode("classpathentry", [ kind : "src", output : source + "-bin", path : source ])
            }
        }

        // Add an extra "ignore optional problems" attribute for external code
        withXml { xml ->
            def node = xml.asNode()
            node."classpathentry".findAll { it.@path.startsWith("external") }.each { entry ->
                entry.appendNode("attributes").appendNode("attribute",  [ name : "ignore_optional_problems", value : "true", ])
            }
        }

        // Add an entry for each library in the list
        withXml { xml ->
            def node = xml.asNode()
            libraries.each() { path, unused ->
                def sourcepath = findSourcepath(path)
                if (sourcepath == null) {
                    node.appendNode("classpathentry", [ kind : "lib", path : path, exported : "false", ])
                } else {
                    node.appendNode("classpathentry", [ kind : "lib", path : path, sourcepath : sourcepath, exported : "false", ])
                }
            }
        }

        // Put all of the "con" classpath entries (like the JRE container) at the bottom
        def all = classpath.entries.findAll { entry -> entry.kind == "con" }
        classpath.entries.removeAll(all)
        withXml { xml ->
            def node = xml.asNode()
            all.findAll { entry ->
                node.appendNode("classpathentry", [ kind : "con", path : entry.path, exported : entry.exported, ])
            }
        }
    }
}

